from captcha_recognizer.recognizer import Recognizer
from selenium import webdriver
from selenium.common import TimeoutException
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import os, logging, time

from selenium.webdriver.support.wait import WebDriverWait

class Browser:
    def __init__(self, driver_path: str, headless: bool = True, implicitly_wait: int = 5) -> None:
        chrome_opts = Options()
        # chrome_opts.add_argument("--disable-gpu")

        # force a big viewport for *both* modes
        chrome_opts.add_argument("--window-size=1920,1080")
        if headless:
            chrome_opts.add_argument("--headless")
        service = Service(executable_path=driver_path)
        self.driver = webdriver.Chrome(service=service, options=chrome_opts)
        self.driver.implicitly_wait(implicitly_wait)
        # 2) In headed mode, truly maximize
        if not headless:
            self.driver.maximize_window()

        # logging.info("Webdriver launched.\n")

    def open_url(self, url: str) -> None:
        # logging.info(f"Opening URL: {url}\n")
        self.driver.get(url)

    def close(self) -> None:
        # logging.info("Close browser.\n")
        self.driver.quit()

    def login(self, login_url: str, username: str, password: str, user_field: tuple, pass_field: tuple, submit_button: tuple) -> None:
        """
        Just opens the login page, fills creds, and clicks submit.
        """

        # navigate to login page
        self.open_url(login_url)

        wait = WebDriverWait(self.driver, 10)

        # switch from QR-code tab to password-login tab
        pwd_tab = wait.until(EC.element_to_be_clickable(
            (By.CSS_SELECTOR, "span[onclick='loginForPwd()']")
        ))
        pwd_tab.click()

        # fill in the fields
        wait.until(EC.visibility_of_element_located(user_field)).send_keys(username)
        wait.until(EC.visibility_of_element_located(pass_field)).send_keys(password)

        self.driver.find_element(*submit_button).click()

    def solve_slider(self, slider_handle: tuple) -> bool:
        """
        1) Locate the knob.
        2) Wait until the puzzle image (fragment) has loaded.
        3) Screenshot the combined puzzle area.
        4) Call compute_offset on that screenshot.
        5) Drag by the returned distance.
        """
        wait = WebDriverWait(self.driver, 10)

        knob = wait.until(EC.element_to_be_clickable(slider_handle))

        wait.until(lambda d: d.execute_script(
            "let img = document.querySelector('.code-front-img');"
            "return img && img.complete && img.naturalWidth > 50;"
        ))

        img_container = self.driver.find_element(By.CSS_SELECTOR, ".code-img")
        os.makedirs("tmp", exist_ok=True)
        capture_path = os.path.join("tmp", "captcha.png")
        img_container.screenshot(capture_path)
        # logging.info(f"Saved puzzle screenshot to {capture_path}")

        # logging.info(f"Identifying drag distance")
        recognizer = Recognizer()
        distance = recognizer.identify_distance_by_screenshot(source=capture_path)

        # logging.info(f"Performing drag. Drag distance: {distance}\n")
        action = ActionChains(self.driver)
        action.click_and_hold(knob) \
            .drag_and_drop_by_offset(knob, distance, 0) \
            .release() \
            .perform()

        return True

    def close_notification_popup(self, timeout: int = 5) -> None:
        """
        If a Bootstrap-style modal with id="myModal" appears,
        close it by clicking either the header × or the footer 关闭 button.
        """
        try:
            wait = WebDriverWait(self.driver, timeout)
            # wait for the modal container to appear
            modal = wait.until(EC.visibility_of_element_located((By.ID, "myModal")))
            # logging.info("Notification popup detected. Closing it...")

            # try the header [data-dismiss="modal"] first
            try:
                close_btn = modal.find_element(By.CSS_SELECTOR, ".modal-header [data-dismiss='modal']")
            except:
                # fallback to the footer button
                close_btn = modal.find_element(By.CSS_SELECTOR, ".modal-footer [data-dismiss='modal']")

            close_btn.click()

            # wait for the modal to go away
            wait.until(EC.invisibility_of_element_located((By.ID, "myModal")))
            # logging.info("Notification popup closed.\n")
        except TimeoutException:
            # no popup appeared, nothing to do
            pass

    def select_course(self, course_suffix: str, timeout: int = 5) -> None:
        """
        Clicks the course tile whose name is “[auto]{course_suffix}”.

        :param course_suffix: the part after “[auto]”, e.g. "14423-软件工程-叶昕源 2024-2025_2"
        :param timeout: how many seconds to wait for the course list to appear
        :raises RuntimeError: if no matching course is found
        """
        target_text = f"[auto]{course_suffix}"
        wait = WebDriverWait(self.driver, timeout)

        elems = wait.until(EC.presence_of_all_elements_located((
            By.CSS_SELECTOR, ".teacher_name.course_name"
        )))

        # logging.info(f"Attempting to navigate to course: {target_text}")

        for elem in elems:
            if elem.text.strip() == target_text:
                tile = elem.find_element(
                    By.XPATH,
                    "./ancestor::div[contains(@class,'right_item')]"
                )
                tile.click()
                return

        # Not found → error
        raise RuntimeError(f"Course named '{target_text}' not found\n")

    def select_submission(self, submission_title: str, timeout: int = 5) -> None:
        """
        Finds and clicks the tree-node matching submission_title, ensuring exact match,
        then waits for the '查看作业提交情况' button to appear.
        :param submission_title: e.g. "作业九：软件WEB部署"
        :param timeout: seconds to wait for the nodes to appear
        :raises RuntimeError: if no matching submission is found
        """
        wait = WebDriverWait(self.driver, timeout)

        xpath_span = (
            f"//span[@class='tree_idx' and normalize-space(text())='{submission_title}']"
        )
        span_elem = wait.until(EC.element_to_be_clickable((By.XPATH, xpath_span)))

        span_elem.click()

        btn_xpath = (
            "//button[contains(@class,'el-button') "
            "and normalize-space(.)='查看作业提交情况']"
        )
        wait.until(EC.element_to_be_clickable((By.XPATH, btn_xpath)))
        # logging.info(f"Submission '{submission_title}' selected correctly.\n")

    def view_submission_details(self, timeout: int = 5) -> None:
        """
        Clicks '查看作业提交情况', waits for the filter form,
        sets the 状态 filter, clicks '查询', and waits for the table rows.
        """
        wait = WebDriverWait(self.driver, timeout)
        # logging.info("Entering view_submission_details()")

        # self.driver.save_screenshot("step1_before_detail.png")
        detail_btn = wait.until(EC.element_to_be_clickable((
            By.XPATH,
            "//button[contains(normalize-space(.), '查看作业提交情况')]"
        )))

        # disable the overlay that’s intercepting clicks
        self.driver.execute_script(
            "document.querySelector('.el-col.el-col-20').style.pointerEvents='none';"
        )

        self.driver.execute_script(
            "document.querySelector('.el-col.el-col-20').style.display='none';"
        )

        detail_btn.click()
        # logging.info("Clicked 查看作业提交情况")
        # self.driver.save_screenshot("step1_after_detail.png")

        wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".el-loading-mask")))
        # logging.info("Initial loading mask gone")

        # wait for the filter form to render
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "form.demo-form-inline")))
        # logging.info("Filter form is present")

        # logging.info("Setting submission status to '已评分'")
        self.set_submission_status("已评分", timeout=5)

        search_btn = wait.until(EC.element_to_be_clickable((
            By.XPATH,
            "//form[contains(@class,'demo-form-inline')]//button[normalize-space(.)='查询']"
        )))
        search_btn.click()
        # logging.info("Clicked 查询")
        # self.driver.save_screenshot("step4_after_search_click.png")

        wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".el-loading-mask")))
        # logging.info("Search loading mask gone")

        wait.until(lambda d: d.execute_script(
            "return document.querySelectorAll('.el-table__body-wrapper tbody tr').length;"
        ) > 0)
        # logging.info("Submission table rows are present.\n")
        # self.driver.save_screenshot("step5_after_rows.png")

    def extract_submissions(self) -> list:
        """
        Extracts student submissions using a JavaScript snippet to avoid stale-element issues.
        Returns a list of dicts with keys: 'student_id', 'student_name', 'submit_time', 'submission_url'.
        """
        # JavaScript that runs in the browser context to scrape the table
        js = '''
        const rows = Array.from(
          document.querySelectorAll('.el-table__body-wrapper tbody tr')
        );
        return rows.map(tr => {
          const tds = tr.querySelectorAll('td');
          // ensure there are enough columns
          if (tds.length < 7) return null;
          const studentId   = tds[1].innerText.trim();
          const studentName = tds[2].innerText.trim();
          const submitTime  = tds[4].innerText.trim();
          const p = tds[6].querySelector('p');
          let link = '';
          if (p) {
            const a = p.querySelector('a');
            link = a ? a.href : p.innerText.trim();
          }
          return {
            student_id: studentId,
            student_name: studentName,
            submit_time: submitTime,
            submission_url: link
          };
        }).filter(item => item !== null);
        '''
        # Execute the script and return the results
        submissions = self.driver.execute_script(js)
        # logging.info(f"Extracted {len(submissions)} submissions via JS.")
        return submissions

    def extract_all_submissions(self, timeout: int = 10) -> list:
        """
        Paginates through all submission pages, collecting every row.
        Stops when the “下一页” arrow is disabled.
        """
        all_subs = []
        wait = WebDriverWait(self.driver, timeout)

        while True:
            # wait for at least one row on the current page
            wait.until(lambda d: d.execute_script(
                "return document.querySelectorAll('.el-table__body-wrapper tbody tr').length > 0"
            ))

            # scrape the current page
            all_subs.extend(self.extract_submissions())

            # locate the “下一页” button
            next_btn = self.driver.find_element(By.CSS_SELECTOR, "button.btn-next")

            # if it’s disabled (last page), break out
            if next_btn.get_attribute("disabled") is not None:
                break

            # snapshot the first row’s text so we can detect a page flip
            old_first = self.driver.execute_script(
                "return document.querySelectorAll('.el-table__body-wrapper tbody tr')[0].innerText;"
            )

            next_btn.click()

            wait.until(lambda d: d.execute_script(
                "return document.querySelectorAll('.el-table__body-wrapper tbody tr')[0].innerText;"
            ) != old_first)

        # logging.info(f"Collected {len(all_subs)} submissions across all pages.")
        return all_subs

    def set_submission_status(self, status_text: str, timeout: int = 10) -> None:
        """
        Opens the 状态 dropdown, logs available options, and selects the one matching status_text.
        """
        # logging.info(">>> entering set_submission_status()")

        wait = WebDriverWait(self.driver, timeout)

        # self.driver.save_screenshot("status_before.png")

        toggle = wait.until(EC.element_to_be_clickable((
            By.XPATH,
            "//label[normalize-space(text())='状态']/following-sibling::div"
            "//div[contains(@class,'el-select')]"
        )))
        toggle.click()

        # time.sleep(0.5)
        # self.driver.save_screenshot("status_opened.png")

        options = self.driver.execute_script("""
          return Array.from(
            document.querySelectorAll('.el-select-dropdown__list li.el-select-dropdown__item')
          ).map(li => li.innerText.trim());
        """)
        # logging.info(f"状态 dropdown options: {options}")

        option_btn = wait.until(EC.element_to_be_clickable((
            By.XPATH,
            f"//ul[contains(@class,'el-select-dropdown__list')]/li[normalize-space(.)='{status_text}']"
        )))
        option_btn.click()

        # time.sleep(0.5)
        # self.driver.save_screenshot("status_after.png")
        # logging.info(f"Selected 状态 = {status_text}")